package com.zaicheng.cloud.remotecall;
/**
 @author zaicheng
 @version 1.0
 @date 2022-04-19
 @email zzc15847256473@163.com
 */

import com.zaicheng.cloud.netty.RpcRequest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

public class ReflectiveRemoteCall extends RemoteCall{

    private final InvocationHandlerFactory factory;

    private final ReflectiveRemoteCall.ParseHandlersByName targetToHandlersByName;

    public ReflectiveRemoteCall(InvocationHandlerFactory factory, ParseHandlersByName targetToHandlersByName) {
        this.factory = factory;
        this.targetToHandlersByName = targetToHandlersByName;
    }

    public static boolean isDefault(Method method) {
//        int SYNTHETIC = true;
        return (method.getModifiers() & 5129) == 1 && method.getDeclaringClass().isInterface();
    }

    @Override
    public  <T> T newInstance(Target<T> target){
        Map<String, InvocationHandlerFactory.MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler>  defaultMethodHandlers = new LinkedList<>();
        Method[] methods = target.type().getMethods();
        int value1 = methods.length;

        for (int value2 = 0; value2 < value1; ++value2){
            Method method = methods[value2];
            if (method.getDeclaringClass() != Object.class){
                if (isDefault(method)){
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method,handler);
                }else {
                   methodToHandler.put(method,nameToHandler.get(RemoteCall.configKey(target.type(),method)));
                }
            }
        }

        InvocationHandler invocationHandler = this.factory.create(target, methodToHandler);
        Object proxyInstance = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, invocationHandler);
        return (T) proxyInstance;
    }


    static final class ParseHandlersByName {

        private final RpcRequest.Options options;
        private final NettyMethodHandler.Factory factory;
        private final Contract contract;

        ParseHandlersByName(RpcRequest.Options options, NettyMethodHandler.Factory factory, Contract contract) {
            this.options = options;
            this.factory = factory;
            this.contract = contract;
        }

        public Map<String, InvocationHandlerFactory.MethodHandler> apply(Target target) {
            List<MethodMetadata> metadata = this.contract.parseAndValidateMetadata(target.type());
            Map<String, InvocationHandlerFactory.MethodHandler> result = new LinkedHashMap();
            Iterator var4 = metadata.iterator();

            while (var4.hasNext()) {
                MethodMetadata md = (MethodMetadata) var4.next();
                Object buildTemplate;
                buildTemplate = new ReflectiveRemoteCall.BuildTemplateByResolvingArgs(md, target);
                if (md.isIgnored()) {
                    result.put(md.configKey(), (target1,m,args) -> {
                        throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
                    });
                } else {
                    result.put(md.configKey(), this.factory.create(target, md,(RequestTemplate.Factory)buildTemplate,this.options));
                }
            }
            return result;
        }
    }

    private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {
        protected final MethodMetadata metadata;
        protected final Target<?> target;
        private final Map<Object, Object> indexToExpander;


        private BuildTemplateByResolvingArgs(MethodMetadata metadata, Target target) {
            this.indexToExpander = new LinkedHashMap();
            this.metadata = metadata;
            this.target = target;
            if (metadata.indexToExpander() != null) {
                this.indexToExpander.putAll(metadata.indexToExpander());
            } else if (!metadata.indexToExpanderClass().isEmpty()) {
                Iterator var4 = metadata.indexToExpanderClass().entrySet().iterator();

                while(var4.hasNext()) {
                    Map.Entry indexToExpanderClass = (Map.Entry)var4.next();

                    try {
                        this.indexToExpander.put(indexToExpanderClass.getKey(), ((Class)indexToExpanderClass.getValue()).newInstance());
                    } catch (InstantiationException var7) {
                        throw new IllegalStateException(var7);
                    } catch (IllegalAccessException var8) {
                        throw new IllegalStateException(var8);
                    }
                }
            }
        }

        @Override
        public RequestTemplate create(Object[] argv) {
            RequestTemplate mutable = RequestTemplate.from(this.metadata.template());
             mutable.remoteCallTarget(this.target);
            if (this.metadata.urlIndex() != null) {
                int urlIndex = this.metadata.urlIndex();
                Util.checkArgument(argv[urlIndex] != null, "URI parameter %s was null", new Object[]{urlIndex});
                mutable.target(String.valueOf(argv[urlIndex]));
            }

            Map<String, Object> varBuilder = new LinkedHashMap();
            Iterator var4 = this.metadata.indexToName().entrySet().iterator();

            while(true) {
                Map.Entry entry;
                int i;
                Object value;
                do {
                    if (!var4.hasNext()) {
                        RequestTemplate template = this.resolve(argv, mutable, varBuilder);
                        if (this.metadata.queryMapIndex() != null) {
                            value = argv[this.metadata.queryMapIndex()];
                            Map<String, Object> queryMap = this.toQueryMap(value);
                            template = this.addQueryMapQueryParameters(queryMap, template);
                        }

//                        if (this.metadata.headerMapIndex() != null) {
//                            template = this.addHeaderMapHeaders((Map)argv[this.metadata.headerMapIndex()], template);
//                        }

                        return template;
                    }

                    entry = (Map.Entry)var4.next();
                    i = (Integer)entry.getKey();
                    value = argv[(Integer)entry.getKey()];
                } while(value == null);

                if (this.indexToExpander.containsKey(i)) {
                    value = this.expandElements((Param.Expander)this.indexToExpander.get(i), value);
                }

                Iterator var8 = ((Collection)entry.getValue()).iterator();

                while(var8.hasNext()) {
                    String name = (String)var8.next();
                    varBuilder.put(name, value);
                }
            }
        }

        private Map<String, Object> toQueryMap(Object value) {
            if (value instanceof Map) {
                return (Map)value;
            } else {
                try {
                    return null; //this.queryMapEncoder.encode(value);
                } catch (Exception var3) {
                    throw new IllegalStateException(var3);
                }
            }
        }

        private Object expandElements(Param.Expander expander, Object value) {
            return value instanceof Iterable ? this.expandIterable(expander, (Iterable)value) : expander.expand(value);
        }

        private List<String> expandIterable(Param.Expander expander, Iterable value) {
            List<String> values = new ArrayList();
            Iterator var4 = value.iterator();

            while(var4.hasNext()) {
                Object element = var4.next();
                if (element != null) {
                    values.add(expander.expand(element));
                }
            }

            return values;
        }

        private RequestTemplate addHeaderMapHeaders(Map<String, Object> headerMap, RequestTemplate mutable) {
            Map.Entry currEntry;
            ArrayList values;
            for(Iterator var3 = headerMap.entrySet().iterator(); var3.hasNext(); mutable.header((String)currEntry.getKey(), values)) {
                currEntry = (Map.Entry)var3.next();
                values = new ArrayList();
                Object currValue = currEntry.getValue();
                if (currValue instanceof Iterable) {
                    Iterator iter = ((Iterable)currValue).iterator();

                    while(iter.hasNext()) {
                        Object nextObject = iter.next();
                        values.add(nextObject == null ? null : nextObject.toString());
                    }
                } else {
                    values.add(currValue == null ? null : currValue.toString());
                }
            }

            return mutable;
        }

        private RequestTemplate addQueryMapQueryParameters(Map<String, Object> queryMap, RequestTemplate mutable) {
            Map.Entry currEntry;
            ArrayList values;
            boolean encoded = false;
            for(Iterator var3 = queryMap.entrySet().iterator(); var3.hasNext(); mutable.query(encoded ? (String)currEntry.getKey() : UriUtils.encode((String)currEntry.getKey()), values)) {
                currEntry = (Map.Entry)var3.next();
                values = new ArrayList();
//                encoded = this.metadata.queryMapEncoded();
                Object currValue = currEntry.getValue();
                if (currValue instanceof Iterable) {
                    Iterator iter = ((Iterable)currValue).iterator();

                    while(iter.hasNext()) {
                        Object nextObject = iter.next();
                        values.add(nextObject == null ? null : (encoded ? nextObject.toString() : UriUtils.encode(nextObject.toString())));
                    }
                } else {
                    values.add(currValue == null ? null : (encoded ? currValue.toString() : UriUtils.encode(currValue.toString())));
                }
            }

            return mutable;
        }

        protected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) {
            return mutable.resolve(variables);
        }
    }


    public static  class  RemoteCallInvocationHandler implements InvocationHandler{

        private  final  Target target;
        private  final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;

        public RemoteCallInvocationHandler(Target target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
            this.target = target;
            this.dispatch = dispatch;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (!"equals".equals(method.getName())) {
                if ("hashCode".equals(method.getName())) {
                    return this.hashCode();
                } else {
                    return "toString".equals(method.getName()) ? this.toString() : ((InvocationHandlerFactory.MethodHandler)this.dispatch.get(method)).invoke(this.target,method,args);
                }
            } else {
                try {
                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return this.equals(otherHandler);
                } catch (IllegalArgumentException var5) {
                    return false;
                }
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof ReflectiveRemoteCall.RemoteCallInvocationHandler) {
                ReflectiveRemoteCall.RemoteCallInvocationHandler other = (ReflectiveRemoteCall.RemoteCallInvocationHandler)obj;
                return this.target.equals(other.target);
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return this.target.hashCode();
        }

        @Override
        public String toString() {
            return this.target.toString();
        }

    }


}
